| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- // @ts-nocheck
- import { zodResolver } from '@hookform/resolvers/zod'
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { LOCAL_STORAGE_KEYS, useFlag, useParams } from 'common'
- import Head from 'next/head'
- import Link from 'next/link'
- import { useRouter } from 'next/router'
- import { PropsWithChildren, useEffect, useMemo, useState } from 'react'
- import { useForm } from 'react-hook-form'
- import { AWS_REGIONS, type CloudProvider } from 'shared-data'
- import { toast } from 'sonner'
- import { Button, Form, useWatch } from 'ui'
- import { Admonition } from 'ui-patterns/admonition'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import { z } from 'zod'
- import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Database/Triggers/EventTriggersList/EventTriggers.constants'
- import { AdvancedConfiguration } from '@/components/interfaces/ProjectCreation/AdvancedConfiguration'
- import { ComputeSizeSelector } from '@/components/interfaces/ProjectCreation/ComputeSizeSelector'
- import { DatabasePasswordInput } from '@/components/interfaces/ProjectCreation/DatabasePasswordInput'
- import { DisabledWarningDueToIncident } from '@/components/interfaces/ProjectCreation/DisabledWarningDueToIncident'
- import { FreeProjectLimitWarning } from '@/components/interfaces/ProjectCreation/FreeProjectLimitWarning'
- import { InternalOnlyConfiguration } from '@/components/interfaces/ProjectCreation/InternalOnlyConfiguration'
- import { OrganizationSelector } from '@/components/interfaces/ProjectCreation/OrganizationSelector'
- import { extractPostgresVersionDetails } from '@/components/interfaces/ProjectCreation/PostgresVersionSelector'
- import { sizes } from '@/components/interfaces/ProjectCreation/ProjectCreation.constants'
- import { FormSchema } from '@/components/interfaces/ProjectCreation/ProjectCreation.schema'
- import {
- instanceLabel,
- smartRegionToExactRegion,
- } from '@/components/interfaces/ProjectCreation/ProjectCreation.utils'
- import { ProjectCreationFooter } from '@/components/interfaces/ProjectCreation/ProjectCreationFooter'
- import { ProjectNameInput } from '@/components/interfaces/ProjectCreation/ProjectNameInput'
- import { RegionSelector } from '@/components/interfaces/ProjectCreation/RegionSelector'
- import { SecurityOptions } from '@/components/interfaces/ProjectCreation/SecurityOptions'
- import {
- GitHubRepositoryField,
- useGitHubRepositoryOptions,
- } from '@/components/interfaces/Settings/Integrations/GithubIntegration/GitHubRepositoryField'
- import DefaultLayout from '@/components/layouts/DefaultLayout'
- import { WizardLayoutWithoutAuth } from '@/components/layouts/WizardLayout'
- import Panel from '@/components/ui/Panel'
- import { useAvailableOrioleImageVersion } from '@/data/config/project-creation-postgres-versions-query'
- import { useOverdueInvoicesQuery } from '@/data/invoices/invoices-overdue-query'
- import { useDefaultRegionQuery } from '@/data/misc/get-default-region-query'
- import { useAuthorizedAppsQuery } from '@/data/oauth/authorized-apps-query'
- import { useFreeProjectLimitCheckQuery } from '@/data/organizations/free-project-limit-check-query'
- import { useOrganizationAvailableRegionsQuery } from '@/data/organizations/organization-available-regions-query'
- import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
- import { DesiredInstanceSize, instanceSizeSpecs } from '@/data/projects/new-project.constants'
- import {
- OrgProject,
- useOrgProjectsInfiniteQuery,
- } from '@/data/projects/org-projects-infinite-query'
- import {
- ProjectCreateVariables,
- useProjectCreateMutation,
- } from '@/data/projects/project-create-mutation'
- import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
- import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { useDataApiRevokeOnCreateDefaultEnabled } from '@/hooks/misc/useDataApiRevokeOnCreateDefault'
- import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
- import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- import { withAuth } from '@/hooks/misc/withAuth'
- import { usePHFlag } from '@/hooks/ui/useFlag'
- import { DOCS_URL, PROJECT_STATUS, PROVIDERS, useDefaultProvider } from '@/lib/constants'
- import { buildStudioPageTitle } from '@/lib/page-title'
- import { useProfile } from '@/lib/profile'
- import { useTrack } from '@/lib/telemetry/track'
- import type { NextPageWithLayout } from '@/types'
- const sizesWithNoCostConfirmationRequired: DesiredInstanceSize[] = ['micro', 'small']
- const Wizard: NextPageWithLayout = () => {
- const track = useTrack()
- const router = useRouter()
- const { slug, projectName } = useParams()
- const { appTitle } = useCustomContent(['app:title'])
- const defaultProvider = useDefaultProvider()
- const { profile } = useProfile()
- const pageTitle = buildStudioPageTitle({
- section: 'New Project',
- brand: appTitle || 'Briven',
- })
- const { data: currentOrg } = useSelectedOrganizationQuery()
- const isFreePlan = currentOrg?.plan?.id === 'free'
- const canChooseInstanceSize = !isFreePlan
- const [lastVisitedOrganization] = useLocalStorageQuery(
- LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
- ''
- )
- const { can: isAdmin } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects')
- const { can: canCreateGitHubConnection } = useAsyncCheckPermissions(
- PermissionAction.CREATE,
- 'integrations.github_connections'
- )
- const showAdvancedConfig = useIsFeatureEnabled('project_creation:show_advanced_config')
- const { hasAccess: hasAccessToGitHubIntegration } = useCheckEntitlements(
- 'integrations.github_connections'
- )
- const smartRegionEnabled = useFlag('enableSmartRegion')
- const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
- const showInternalOnlyConfiguration = useFlag('newProjectInternalOnlyConfiguration')
- // Read the raw flag for telemetry — coerce-undefined-to-false would record false for
- // users whose flags haven't loaded yet. The raw value preserves undefined (omitted from
- // PostHog) so we only record true/false when the flag is resolved.
- const dataApiRevokeOnCreateDefaultFlag = usePHFlag<boolean>('dataApiRevokeOnCreateDefault')
- const isDataApiRevokeOnCreateDefault = useDataApiRevokeOnCreateDefaultEnabled()
- const isNotOnHigherPlan = !['team', 'enterprise', 'platform'].includes(currentOrg?.plan.id ?? '')
- // This is to make the database.new redirect work correctly. The database.new redirect should be set to supabase.com/dashboard/new/last-visited-org
- if (slug === 'last-visited-org') {
- if (lastVisitedOrganization) {
- router.replace(`/new/${lastVisitedOrganization}`, undefined, { shallow: true })
- } else {
- router.replace(`/new/_`, undefined, { shallow: true })
- }
- }
- const [allProjects, setAllProjects] = useState<OrgProject[] | undefined>(undefined)
- const [isComputeCostsConfirmationModalVisible, setIsComputeCostsConfirmationModalVisible] =
- useState(false)
- const form = useForm<z.infer<typeof FormSchema>>({
- resolver: zodResolver(FormSchema as any),
- mode: 'onChange',
- defaultValues: {
- organization: slug,
- projectName: projectName || '',
- highAvailability: false,
- postgresVersion: '',
- instanceType: '',
- cloudProvider: PROVIDERS[defaultProvider].id,
- dbPass: '',
- dbPassStrength: 0,
- dbPassStrengthMessage: '',
- dbRegion: undefined,
- githubRepositoryId: '',
- githubInstallationId: undefined,
- githubRepositoryName: '',
- instanceSize: canChooseInstanceSize ? sizes[0] : undefined,
- dataApi: true,
- dataApiDefaultPrivileges: !isDataApiRevokeOnCreateDefault,
- enableRlsEventTrigger: false,
- postgresVersionSelection: '',
- useOrioleDb: false,
- },
- })
- const { getFieldState, resetField, setValue } = form
- const {
- instanceSize: watchedInstanceSize,
- cloudProvider,
- dbRegion,
- githubRepositoryName,
- organization,
- projectName: watchedProjectName,
- highAvailability,
- } = useWatch({ control: form.control })
- // Read dirty state during render rather than depending on form.formState in the
- // effect — form.formState is a Proxy that gets a new reference every render, which
- // would re-fire this effect after each setValue and trigger an infinite loop.
- const isDataApiDefaultPrivilegesDirty = getFieldState(
- 'dataApiDefaultPrivileges',
- form.formState
- ).isDirty
- useEffect(() => {
- if (dataApiRevokeOnCreateDefaultFlag === undefined) return
- if (isDataApiDefaultPrivilegesDirty) return
- setValue('dataApiDefaultPrivileges', !dataApiRevokeOnCreateDefaultFlag, {
- shouldDirty: false,
- })
- }, [dataApiRevokeOnCreateDefaultFlag, isDataApiDefaultPrivilegesDirty, setValue])
- // [Charis] Since the form is updated in a useEffect, there is an edge case
- // when switching from free to paid, where canChooseInstanceSize is true for
- // an in-between render, but watchedInstanceSize is still undefined from the
- // form state carried over from the free plan. To avoid this, we set a
- // default instance size in this case.
- const instanceSize = canChooseInstanceSize ? (watchedInstanceSize ?? sizes[0]) : undefined
- const { data: membersExceededLimit = [] } = useFreeProjectLimitCheckQuery(
- { slug },
- { enabled: isFreePlan }
- )
- const hasMembersExceedingFreeTierLimit = membersExceededLimit.length > 0
- const freePlanWithExceedingLimits = isFreePlan && hasMembersExceedingFreeTierLimit
- const { data: organizations = [], isSuccess: isOrganizationsSuccess } = useOrganizationsQuery()
- const isEmptyOrganizations = isOrganizationsSuccess && organizations.length <= 0
- const { data: approvedOAuthApps = [] } = useAuthorizedAppsQuery(
- { slug },
- { enabled: !isFreePlan && slug !== '_' }
- )
- const hasOAuthApps = approvedOAuthApps.length > 0
- const { data: allOverdueInvoices = [] } = useOverdueInvoicesQuery({
- enabled: isNotOnHigherPlan,
- })
- const overdueInvoices = allOverdueInvoices.filter((x) => x.organization_id === currentOrg?.id)
- const hasOutstandingInvoices = isNotOnHigherPlan && overdueInvoices.length > 0
- const { data: orgProjectsFromApi } = useOrgProjectsInfiniteQuery({ slug: currentOrg?.slug })
- const allOrgProjects = useMemo(
- () => orgProjectsFromApi?.pages.flatMap((page) => page.projects),
- [orgProjectsFromApi?.pages]
- )
- const organizationProjects =
- allProjects?.filter((project) => project.status !== PROJECT_STATUS.INACTIVE) ?? []
- const availableComputeCredits = organizationProjects.length === 0 ? 10 : 0
- const additionalMonthlySpend = isFreePlan
- ? 0
- : instanceSizeSpecs[instanceSize as DesiredInstanceSize]!.priceMonthly - availableComputeCredits
- const { data: _defaultRegion, error: defaultRegionError } = useDefaultRegionQuery(
- {
- cloudProvider: PROVIDERS[defaultProvider].id,
- },
- {
- enabled: !smartRegionEnabled,
- refetchOnMount: false,
- refetchOnWindowFocus: false,
- refetchInterval: false,
- refetchOnReconnect: false,
- retry: false,
- }
- )
- const { data: availableRegionsData, error: availableRegionsError } =
- useOrganizationAvailableRegionsQuery(
- {
- slug: slug,
- cloudProvider: PROVIDERS[cloudProvider as CloudProvider].id,
- desiredInstanceSize: instanceSize as DesiredInstanceSize,
- },
- {
- enabled: smartRegionEnabled,
- refetchOnMount: false,
- refetchOnWindowFocus: false,
- refetchInterval: false,
- refetchOnReconnect: false,
- }
- )
- const recommendedSmartRegion = smartRegionEnabled
- ? availableRegionsData?.recommendations.smartGroup.name
- : ''
- const regionError =
- smartRegionEnabled && defaultProvider !== 'AWS_NIMBUS'
- ? availableRegionsError
- : defaultRegionError
- const defaultRegion =
- defaultProvider === 'AWS_NIMBUS'
- ? AWS_REGIONS.EAST_US.displayName
- : smartRegionEnabled
- ? availableRegionsData?.recommendations.smartGroup.name
- : _defaultRegion
- const canCreateProject = isAdmin && !freePlanWithExceedingLimits && !hasOutstandingInvoices
- const canConfigureGitHubOnCreate =
- canCreateProject && hasAccessToGitHubIntegration && canCreateGitHubConnection
- const dbRegionExact = smartRegionToExactRegion(dbRegion ?? '')
- const availableOrioleVersion = useAvailableOrioleImageVersion(
- {
- cloudProvider: cloudProvider as CloudProvider,
- dbRegion: smartRegionEnabled ? dbRegionExact : (dbRegion ?? ''),
- organizationSlug: organization,
- },
- { enabled: currentOrg !== null }
- )
- const userPrimaryEmail = profile?.primary_email?.toLowerCase()
- const isUserAtFreeProjectLimit = userPrimaryEmail
- ? membersExceededLimit.some(
- (member) => member.primary_email?.toLowerCase() === userPrimaryEmail
- )
- : false
- const shouldShowFreeProjectInfo = !!currentOrg && !isFreePlan && !isUserAtFreeProjectLimit
- const {
- gitHubAuthorization,
- githubRepos,
- hasPartialResponseDueToSSO,
- isLoading: isLoadingRepositoryOptions,
- refetch: refetchRepositoryOptions,
- } = useGitHubRepositoryOptions()
- const {
- mutate: createProject,
- isPending: isCreatingNewProject,
- isSuccess: isSuccessNewProject,
- } = useProjectCreateMutation({
- onSuccess: (res) => {
- track(
- 'project_creation_simple_version_submitted',
- {
- surface: 'main',
- instanceSize: form.getValues('instanceSize'),
- enableRlsEventTrigger: form.getValues('enableRlsEventTrigger'),
- dataApiEnabled: form.getValues('dataApi'),
- dataApiDefaultPrivilegesGranted: form.getValues('dataApiDefaultPrivileges'),
- useOrioleDb: form.getValues('useOrioleDb'),
- ...(dataApiRevokeOnCreateDefaultFlag !== undefined && {
- dataApiRevokeOnCreateDefaultEnabled: dataApiRevokeOnCreateDefaultFlag,
- }),
- },
- {
- project: res.ref,
- organization: res.organization_slug,
- }
- )
- router.push(`/project/${res.ref}`)
- },
- })
- const onSubmitWithComputeCostsConfirmation = async (values: z.infer<typeof FormSchema>) => {
- const launchingLargerInstance =
- values.instanceSize &&
- !sizesWithNoCostConfirmationRequired.includes(values.instanceSize as DesiredInstanceSize)
- if (additionalMonthlySpend > 0 && (hasOAuthApps || launchingLargerInstance)) {
- track('project_creation_simple_version_confirm_modal_opened', {
- instanceSize: values.instanceSize,
- })
- setIsComputeCostsConfirmationModalVisible(true)
- } else {
- await onSubmit(values)
- }
- }
- const onSubmit = async (values: z.infer<typeof FormSchema>) => {
- if (!currentOrg) return console.error('Unable to retrieve current organization')
- const {
- cloudProvider,
- projectName,
- highAvailability,
- dbPass,
- dbRegion,
- postgresVersion,
- instanceType,
- instanceSize,
- dataApi,
- dataApiDefaultPrivileges,
- enableRlsEventTrigger,
- postgresVersionSelection,
- useOrioleDb,
- githubInstallationId,
- githubRepositoryId,
- } = values
- if (useOrioleDb && !availableOrioleVersion) {
- return toast.error('No available OrioleDB image found, only Postgres is available')
- }
- const { postgresEngine, releaseChannel } =
- extractPostgresVersionDetails(postgresVersionSelection)
- const { smartGroup = [], specific = [] } = availableRegionsData?.all ?? {}
- const selectedRegion = smartRegionEnabled
- ? (smartGroup.find((x) => x.name === dbRegion) ?? specific.find((x) => x.name === dbRegion))
- : undefined
- const parsedGitHubRepositoryId =
- githubRepositoryId.length > 0 ? Number(githubRepositoryId) : undefined
- const shouldIncludeGitHubFields =
- githubInstallationId !== undefined && Number.isFinite(parsedGitHubRepositoryId)
- const data: ProjectCreateVariables = {
- dbPass,
- cloudProvider,
- organizationSlug: currentOrg.slug,
- name: projectName,
- highAvailability,
- // gets ignored due to org billing subscription anyway
- dbPricingTierId: 'tier_free',
- // only set the compute size on pro+ plans. Free plans always use micro (nano in the future) size.
- dbInstanceSize: isFreePlan ? undefined : (instanceSize as DesiredInstanceSize),
- dataApiExposedSchemas: !dataApi ? [] : undefined,
- dataApiUseApiSchema: false,
- dataApiRevokeDefaultPrivileges: dataApi && !dataApiDefaultPrivileges,
- postgresEngine: useOrioleDb ? availableOrioleVersion?.postgres_engine : postgresEngine,
- releaseChannel: useOrioleDb ? availableOrioleVersion?.release_channel : releaseChannel,
- ...(smartRegionEnabled ? { regionSelection: selectedRegion } : { dbRegion }),
- dbSql: enableRlsEventTrigger ? AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL : undefined,
- ...(shouldIncludeGitHubFields
- ? {
- githubInstallationId,
- githubRepositoryId: parsedGitHubRepositoryId,
- }
- : {}),
- }
- if (postgresVersion && !postgresVersion.match(/1[2-9]\..*/)) {
- toast.error(
- `Invalid Postgres version, should start with a number between 12-19, a dot and additional characters, i.e. 15.2 or 15.2.0-3`
- )
- }
- if (postgresVersion || instanceType) {
- data['customBrivenRequest'] = {
- ami: {
- ...(postgresVersion && {
- search_tags: { 'tag:postgresVersion': postgresVersion },
- }),
- ...(instanceType && { instance_type: instanceType }),
- },
- }
- }
- createProject(data)
- }
- useEffect(() => {
- // Only set once to ensure compute credits dont change while project is being created
- if (allOrgProjects && allOrgProjects.length > 0 && !allProjects) {
- setAllProjects(allOrgProjects)
- }
- }, [allOrgProjects, allProjects, setAllProjects])
- useEffect(() => {
- // Handle no org: redirect to new org route
- if (isEmptyOrganizations) {
- router.push(`/new`)
- }
- }, [isEmptyOrganizations, router])
- useEffect(() => {
- // [Joshen] Cause slug depends on router which doesnt load immediately on render
- // While the form data does load immediately
- if (slug && slug !== '_') setValue('organization', slug)
- if (projectName) setValue('projectName', projectName || '')
- }, [slug, setValue, projectName])
- const isDbRegionDirty = getFieldState('dbRegion', form.formState).isDirty
- useEffect(() => {
- if (!isDbRegionDirty && defaultRegion) {
- setValue('dbRegion', defaultRegion)
- }
- }, [defaultRegion, isDbRegionDirty, setValue])
- useEffect(() => {
- if (regionError) {
- resetField('dbRegion', {
- defaultValue: PROVIDERS[defaultProvider].default_region.displayName,
- })
- }
- }, [regionError, resetField, defaultProvider])
- useEffect(() => {
- if (!isDbRegionDirty && recommendedSmartRegion) {
- setValue('dbRegion', recommendedSmartRegion)
- }
- }, [recommendedSmartRegion, isDbRegionDirty, setValue])
- useEffect(() => {
- if (highAvailability && cloudProvider !== 'AWS_K8S') {
- setValue('cloudProvider', 'AWS_K8S')
- }
- }, [highAvailability, cloudProvider, setValue])
- useEffect(() => {
- if (watchedInstanceSize !== instanceSize) {
- setValue('instanceSize', instanceSize, {
- shouldDirty: false,
- shouldValidate: false,
- shouldTouch: false,
- })
- }
- }, [instanceSize, watchedInstanceSize, setValue])
- useEffect(() => {
- if (!githubRepositoryName) return
- if ((watchedProjectName ?? '').trim().length > 0) return
- const repoName = githubRepositoryName.split('/').at(-1) ?? githubRepositoryName
- setValue('projectName', repoName.trim(), {
- shouldValidate: true,
- })
- }, [githubRepositoryName, watchedProjectName, setValue])
- return (
- <>
- {/* Wizard layouts set the visual header but not the browser tab title. */}
- <Head>
- <title>{pageTitle}</title>
- <meta name="description" content="Briven Studio" />
- </Head>
- <Form {...form}>
- <form onSubmit={form.handleSubmit(onSubmitWithComputeCostsConfirmation)}>
- <Panel
- loading={!isOrganizationsSuccess}
- title={
- <div key="panel-title">
- <h3>Create a new project</h3>
- <p className="text-sm text-foreground-lighter text-balance">
- Your project will have its own dedicated instance and full Postgres database. An
- API will be set up so you can easily interact with your new database.
- </p>
- </div>
- }
- footer={
- <ProjectCreationFooter
- form={form}
- canCreateProject={canCreateProject}
- instanceSize={instanceSize}
- organizationProjects={organizationProjects}
- isCreatingNewProject={isCreatingNewProject}
- isSuccessNewProject={isSuccessNewProject}
- />
- }
- >
- <>
- {projectCreationDisabled ? (
- <DisabledWarningDueToIncident title="Project creation is currently disabled" />
- ) : (
- <div className="divide-y divide-border-border">
- <OrganizationSelector form={form} />
- {canCreateProject && (
- <>
- {canConfigureGitHubOnCreate && (
- <Panel.Content>
- <GitHubRepositoryField
- form={form}
- name="githubRepositoryId"
- installationIdField="githubInstallationId"
- repositoryNameField="githubRepositoryName"
- label="GitHub (optional)"
- description={
- <>
- Ideal for agent-first workflows: update your schema in code, push it
- to GitHub, and Briven deploys the changes automatically.{' '}
- <a
- href="https://supabase.com/docs/guides/deployment/branching/github-integration"
- target="_blank"
- rel="noreferrer noopener"
- className="text-link"
- >
- Learn more
- </a>
- </>
- }
- disabled={isCreatingNewProject}
- repositories={githubRepos}
- gitHubAuthorization={gitHubAuthorization}
- hasPartialResponseDueToSSO={hasPartialResponseDueToSSO}
- isLoading={isLoadingRepositoryOptions}
- refetch={refetchRepositoryOptions}
- onConnectClick={() => track('project_creation_github_connect_clicked')}
- />
- </Panel.Content>
- )}
- <ProjectNameInput form={form} />
- {canChooseInstanceSize && <ComputeSizeSelector form={form} />}
- <DatabasePasswordInput form={form} />
- <RegionSelector
- form={form}
- instanceSize={instanceSize as DesiredInstanceSize}
- />
- <SecurityOptions form={form} />
- {showInternalOnlyConfiguration && <InternalOnlyConfiguration form={form} />}
- {showAdvancedConfig && !!availableOrioleVersion && (
- <AdvancedConfiguration form={form} />
- )}
- {shouldShowFreeProjectInfo ? (
- <Admonition
- className="rounded-none border-0"
- type="note"
- title="Need a free project?"
- description={
- <p>
- You can have up to 2 free projects across all organizations.{' '}
- <Link className="underline text-foreground" href="/new">
- Create a free organization
- </Link>{' '}
- to use them.
- </p>
- }
- />
- ) : null}
- </>
- )}
- {freePlanWithExceedingLimits ? (
- isAdmin &&
- slug && (
- <FreeProjectLimitWarning membersExceededLimit={membersExceededLimit || []} />
- )
- ) : hasOutstandingInvoices ? (
- <Panel.Content>
- <Admonition
- type="default"
- title="Your organization has overdue invoices"
- description={
- <div className="space-y-3">
- <p className="text-sm leading-normal">
- Please resolve all outstanding invoices first before creating a new
- project
- </p>
- <div>
- <Button asChild type="default">
- <Link href={`/org/${slug}/billing#invoices`}>View invoices</Link>
- </Button>
- </div>
- </div>
- }
- />
- </Panel.Content>
- ) : null}
- </div>
- )}
- </>
- </Panel>
- <ConfirmationModal
- size="large"
- loading={false}
- visible={isComputeCostsConfirmationModalVisible}
- title="Confirm compute costs"
- confirmLabel="I understand"
- onCancel={() => setIsComputeCostsConfirmationModalVisible(false)}
- onConfirm={async () => {
- const values = form.getValues()
- await onSubmit(values)
- setIsComputeCostsConfirmationModalVisible(false)
- }}
- variant={'warning'}
- >
- <div className="text-sm text-foreground-light space-y-1">
- <p>
- Launching a project on compute size "{instanceLabel(instanceSize)}" increases your
- monthly costs by ${additionalMonthlySpend}, independent of how actively you use it.
- By clicking "I understand", you agree to the additional costs.{' '}
- <Link
- href={`${DOCS_URL}/guides/platform/manage-your-usage/compute`}
- target="_blank"
- className="underline"
- >
- Compute Costs
- </Link>{' '}
- are non-refundable.
- </p>
- </div>
- </ConfirmationModal>
- </form>
- </Form>
- </>
- )
- }
- const PageLayout = withAuth(({ children }: PropsWithChildren) => {
- return <WizardLayoutWithoutAuth>{children}</WizardLayoutWithoutAuth>
- })
- Wizard.getLayout = (page) => (
- <DefaultLayout hideMobileMenu headerTitle="New project">
- <PageLayout>{page}</PageLayout>
- </DefaultLayout>
- )
- export default Wizard
|